﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace AdaptiveDeNet.RAND
{
    public class ALFG
    {
        

        private long[] lagTable = new long[55];
        const double IntToDoubleMultiplier = 1.0 / (2147483648.0);

        private int _j = 24;
        private int _k = 55;
        private int n = 55;

        //private int decrModulus = 31;
        //private double modulus = 32;
        private long MODULUS = 2147483648;

        private long[,] A = new long[55, 55];
        private static long[,] identMatrix = new long[55, 55];
        private long[,] temp = new long[55, 55];

        private Object locker = new Object();
        private static bool firstStream = true;
        private static long[] sharedTable = new long[55];

        public ALFG()
        {
            lock (locker)
            {
                if (firstStream)
                {
                    //init table
                    MersenneTwister rand = new MersenneTwister((ulong)DateTime.Now.Ticks);

                    for (int i = 0; i < lagTable.Length; i++)lagTable[i] = rand.genrand_N(1073741823);
                    

                    Buffer.BlockCopy(lagTable, 0, sharedTable, 0, _k * sizeof(long));

                    this.initA(A);
                    this.initIdent();
                    this.mpow(4294967296);
                    firstStream = false;
                }
                else
                {
                    Buffer.BlockCopy(sharedTable, 0, lagTable, 0, _k * sizeof(long));
                    this.vectMul();   
                }
            }
        }

        public void initTable() 
        {
            MersenneTwister rand = new MersenneTwister((ulong)DateTime.Now.Ticks);

            for (int i = 0; i < lagTable.Length; i++)
            {
                lagTable[i] = rand.genrand_N(1073741823); 

                //lagTable[i] = i+ 1; 
            }

        }

        private void initA(long[,] A)
        {
            for (int i = 0; i < this.n; i++)
            {
                for (int j = 0; j < this.n; j++)
                {
                    A[i, j] = 0;
                }
            }

            //diagonala

            for (int i = 1, j = 0; i < this.n; i++, j++)
            {
                A[i, j] = 1;
            }

            A[0, this.n - 1] = 1;
            A[0, this._j - 1] = 1;
        }

        private void fillZeroes(long[,] dest)
        {
            for (int i = 0; i < this.n; i++)
            {
                for (int j = 0; j < this.n; j++)
                {
                    dest[i, j] = 0;
                }
            }    
        }

        public void jumpAhead(int num)
        {
            this.initA(A);
            this.initIdent();
            this.mpow(num);
            this.vectMul();
        }

        private void initIdent() 
        {
            for (int i = 0; i < this.n; i++)
            {
                for (int j = 0; j < this.n; j++)
                {
                    identMatrix[i, j] = 0;
                }
            }

            for (int i = 0, j = 0; i < this.n; i++, j++)
            {
                identMatrix[i, j] = 1;
            }
        }

        private long[,] mpow(long exp)
        {
            while (exp > 0)
            {
                if ((exp & 1) == 1)
                {
                    temp = mmult(identMatrix, A);
                    for (int i = 0; i < this.n; i++)
                    {
                        for (int j = 0; j < this.n; j++)
                        {
                            identMatrix[i, j] = temp[i, j];    
                        }
                    }
                    fillZeroes(temp);
                    exp--;
                }


                temp = mmult(A, A);
                for (int i = 0; i < this.n; i++)
                {
                    for (int j = 0; j < this.n; j++)
                    {
                        A[i, j] = temp[i, j];
                    }
                }
                fillZeroes(temp);
                exp /= 2;
            }

            return A;
        
        }

        private void vectMul()
        {
            fillZeroes(temp);

            long sum = 0;

            for (int i = 0; i < this.n; i++)
            {
                sum = 0;
                for (int j = 0; j < this.n; j++)
                {
                    sum += identMatrix[i, j] * lagTable[j];
                    sum = sum % MODULUS;
                    temp[i, 0] = sum;
                }
                
            }

            for (int i = 0; i < this.n; i++)lagTable[i] = temp[i, 0];
        }

        private long[,] mmod(long[,] A)
        {
            long[,] ans = new long[55, 55];

            for (int i = 0; i < this.n; i++)
            {
                for (int j = 0; j < this.n; j++)
                {
                    ans[i, j] = A[i, j] % MODULUS;
                }
            }

            return ans;
        }

        private long[,] mmult(long[,] A, long[,] B)
        {
            long[,] ans = new long[55, 55];

            for (int i = 0; i < this.n; i++)
            {
                for (int j = 0; j < this.n; j++)
                {
                    for (int k = 0; k < this.n; k++)
                    {
                        ans[i, j] += A[i, k] * B[k, j];
                        ans[i,j] = ans[i, j] % MODULUS;
                    }
                }
            }

            return ans;

        }

        public double nextDouble() 
        {
            long Xn = (lagTable[_j - 1] + lagTable[_k - 1]) % MODULUS;

            //for (int a = this.n - 1, b = this.n - 2; b >= 0; a--, b--)
            //{
            //    lagTable[a] = lagTable[b];
            //}

            lagTable[_k - 1 ] = Xn;

            --_j;
            --_k;

            if (_j == 0)
            {
                _j = 55;
            }
            else
            {
                if (_k == 0)
                {
                    _k = 55;
                }
            }

            return (Xn * IntToDoubleMultiplier);
        }

        public int nextInt(int iN) 
        {
            long Xn = (lagTable[_j - 1] + lagTable[_k - 1]) % MODULUS;

            //for (int a = this.n - 1, b = this.n - 2; b >= 0; a--, b--)
            //{
            //    lagTable[a] = lagTable[b];
            //}

            lagTable[_k - 1] = Xn;

            --_j;
            --_k;

            if (_j == 0)
            {
                _j = 55;
            }
            else
            {
                if (_k == 0)
                {
                    _k = 55;
                }
            }

            return (int)(Xn * (iN / 2147483648.0));
        }

        public void printA()
        {
            Console.WriteLine();

            for (int i = 0; i < this.n; i++)
            {
                for (int j = 0; j < this.n; j++)
                {
                    Console.Write(identMatrix[i, j] + "");    
                }
                Console.WriteLine();
            }

            Console.WriteLine(";");
        }


        public long[] getlVect()
        {
            return this.lagTable;
        }

        public void setlVect(long[] input)
        {
            for (int i = 0; i < this.n; i++)
            {
                lagTable[i] = input[i];
            }        
        }

        public bool TablesAreEqual(long[] input)
        {
            bool ret = true;

            for (int i = 0; i < lagTable.Length; i++)
            {
                if (input[i] != this.lagTable[i]) return false;
            }

            return ret;
        }

        private CountdownEvent countdown = new CountdownEvent(2);

        public void Test()
        {
            int a = 0;
            double b = 0.0;

            new Thread(() =>
            {
                for (int i = 0; i < 1E6; i++) a++;
                countdown.Signal();
            }).Start();

            new Thread(() =>
            {
                for (int i = 0; i < 1E6; i++) b++;
                countdown.Signal();
            }).Start();

            countdown.Wait();
        }
    }
}
